import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.ObjectInputStream.GetField;
import java.util.Iterator;

/*
 * Issues to deal with
 * 1. EOL
 * 2. File Handling
 * 		2.1 How do plan to buffer, or will you read all of it at once
 */

public class LexicalAnalyzer {

	static final String LETTERSET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
	static final String DIGITSET = "0123456789";
	static final String OPERATORSET = "+-*<>&.@/:=~|$!#%^_[]{}\"`?";
	static final String PUNCTIONSET = "();,";
	static final String COMMENTSET = "'();,// \t";
	static final String SPACESSET = " \t\n";
	static final String RESERVED[] = {"let","in","fn","where","aug",
										"or","not","gr","ge","ls",
										"le","eq","ne","true","false",
										"nil","dummy","within","rec","and"};
	
	static final int FSM_START = 0;
	static final int IDENTIFIER = 1;
	static final int INTEGER = 2;
	static final int OPERATOR = 3;
	static final int STRING = 4;
	static final int DELETE = 5;

	int state = FSM_START;
	int streamPointer = 0;
	//String inputProg = "true & false -> Print('digi' aug 'fish') | (+4*8+3**7/6) ";
	//String inputProg = "fn ().1+2 ";
	//String inputProg = "let a = 2 in Print(a) ";
	String inputProg;

	public LexicalAnalyzer(String str, String str2) {
		inputProg = " let diggy a b = (2,3)	in 	Print ( diggy 2 3 ) ";
	}

	public LexicalAnalyzer(String fileName) {
		inputProg = initInput(fileName);
		//System.out.println(inputProg);
	}

	private String initInput(String fileName) {
		try {
			BufferedReader in = new BufferedReader(new FileReader(new File(fileName)));
			String str = "";
			String output = "";
			while((str = in.readLine())!=null){
				output += str + "\n";
			}
			return output;
		} catch (FileNotFoundException e) {
			e.printStackTrace();
			System.err.println("file not found");
			System.exit(-1);
		} catch (IOException e) {
			e.printStackTrace();
			System.err.println("IO Exception Thrown");
			System.exit(-1);
		} 
		return null;
	}

	public static void main(String[] args) {
		// call for next token one by one
		LexicalAnalyzer lx = new LexicalAnalyzer("c:\\matrix_order1");
		for (int i = 0; i < lx.inputProg.length(); i++) {
			System.out.println(lx.getNextToken());
		}
		System.out.println(lx.getNextToken());
	}

	public Token getNextToken() {
		ignoreSpaces();
		ignoreCommentToken();
		ignoreSpaces();
		if (streamPointer >= inputProg.length()) {
			return createErrorToken("Error getNextToken: Input Finished, no more token left");
		}

		if (isLetter(inputProg.charAt(streamPointer))) {
			// it will return ReservedWord Token if the token is eventually a reserved word
			return createIdToken();
		} else if (isDigit(inputProg.charAt(streamPointer))) {
			return createIntegerToken();
		} else if (isOperator(inputProg.charAt(streamPointer))) {
			return createOperatorToken();
		} else if (isPunctuation(inputProg.charAt(streamPointer))) {
			return createPunctuationToken();
		} else if (isString(inputProg.charAt(streamPointer))) {
			return createStringToken();
		} else if (isComment(inputProg.charAt(streamPointer),
				inputProg.charAt(streamPointer + 1))) {
			// if it doesn't return an error token than return a the next token
			Token cmtToken = ignoreCommentToken();
			if (cmtToken.type == 7)
				return cmtToken;
			else
				return getNextToken();
		} else {
			return createErrorToken("Error getNextToken: Input not recognized '"
					+ inputProg.substring(streamPointer) + "'");
		}
	}

	/*
	 * This Function need to be tested for EOL
	 */
	private Token ignoreCommentToken() {
		String str = "";
		// read "//" but check if an end of input is not coming
		// if not a comment than its fine, otherwise we consume the whole line of comment
		if ((streamPointer+1) >= inputProg.length()){
			return null;
		}else if (!isComment(inputProg.charAt(streamPointer), inputProg.charAt(streamPointer+1))){
			return null;
		}
		streamPointer++;
		streamPointer++;
		while (inputProg.charAt(streamPointer) == '\''
				| inputProg.charAt(streamPointer) == '('
				| inputProg.charAt(streamPointer) == ')'
				| inputProg.charAt(streamPointer) == ';'
				| inputProg.charAt(streamPointer) == ','
				| inputProg.charAt(streamPointer) == '\\'
				| inputProg.charAt(streamPointer) == ' '
				| inputProg.charAt(streamPointer) == '\t'
				| isLetter(inputProg.charAt(streamPointer))
				| isDigit(inputProg.charAt(streamPointer))
				| isOperator(inputProg.charAt(streamPointer))) {
			str += inputProg.charAt(streamPointer);
			streamPointer++;
		}
		// now closing EOL
		// keep removing comments in the subsiquesnt lines too
		if (inputProg.charAt(streamPointer) == '\n') {
			streamPointer++;
			ignoreCommentToken();
			return new Token(3, str);
		} else if (inputProg.charAt(streamPointer) == '\r'
				&& inputProg.charAt(streamPointer + 1) == '\n') {
			streamPointer++;
			streamPointer++;
			ignoreCommentToken();
			return new Token(3, str);
		} else
			return new Token(7, "Comment not terminated properly:" + str + "@");
	}

	private Token createStringToken() {
		String str = "";
		// read ''''
		streamPointer++;
		while ((inputProg.charAt(streamPointer) == '\\' && inputProg
				.charAt(streamPointer) == 't')
				| (inputProg.charAt(streamPointer) == '\\' && inputProg
						.charAt(streamPointer) == 'n')
				| (inputProg.charAt(streamPointer) == '\\' && inputProg
						.charAt(streamPointer) == '\\')
				| (inputProg.charAt(streamPointer) == '\\' && inputProg
						.charAt(streamPointer) == '\'')
				| inputProg.charAt(streamPointer) == '('
				| inputProg.charAt(streamPointer) == ')'
				| inputProg.charAt(streamPointer) == ';'
				| inputProg.charAt(streamPointer) == ','
				| inputProg.charAt(streamPointer) == ' '
				| isLetter(inputProg.charAt(streamPointer))
				| isDigit(inputProg.charAt(streamPointer))
				| isOperator(inputProg.charAt(streamPointer))) {
			str += inputProg.charAt(streamPointer);
			streamPointer++;
		}
		// now closing '''' are needed else error
		if (isString(inputProg.charAt(streamPointer))) {
			streamPointer++;
			return new Token(3, str);
		} else
			return new Token(7, "Close brakets expected for the string");
	}

	private boolean isString(char startChar) {
		if (startChar == '\'')
			return true;
		else
			return false;
	}

	private Token createPunctuationToken() {
		String punc = "" + inputProg.charAt(streamPointer);
		streamPointer++;
		return new Token(6, punc);
	}

	private boolean isPunctuation(char startChar) {
		for (int i = 0; i < PUNCTIONSET.length(); i++) {
			if (startChar == PUNCTIONSET.charAt(i))
				return true;
		}
		return false;
	}

	private Token createOperatorToken() {
		String integer = "";
		while (isOperator(inputProg.charAt(streamPointer))) {
			integer += inputProg.charAt(streamPointer);
			streamPointer++;
		}
		return new Token(2, integer);
	}

	private Token createIntegerToken() {
		String integer = "";
		while (isDigit(inputProg.charAt(streamPointer))) {
			integer += inputProg.charAt(streamPointer);
			streamPointer++;
		}
		return new Token(1, integer);
	}

	private Token createErrorToken(String string2) {
		return new Token(7, string2);
	}

	private void ignoreSpaces() {
		// Condisional AND shows short cuicuit behavior and onlly eval right if
		// left is true
		while (streamPointer < inputProg.length()
				&& isSpaces(inputProg.charAt(streamPointer))) {
			streamPointer++;
		}
	}

	/*
	 * I will have to handle EOL char or test it more for now
	 */
	private boolean isSpaces(char startChar) {
		// handle eol,
		for (int i = 0; i < SPACESSET.length(); i++) {
			if (startChar == SPACESSET.charAt(i))
				return true;
		}
		return false;
	}

	private Token createIdToken() {
		String id = "";
		while (isLetter(inputProg.charAt(streamPointer))
				| isDigit(inputProg.charAt(streamPointer))
				| inputProg.charAt(streamPointer) == '_') {
			id += inputProg.charAt(streamPointer);
			streamPointer++;
		}
		if (isReserved(id)){
			return new Token(8, id);
		}else{
			return new Token(0, id);
		}
	}

	private boolean isReserved(String id) {
		for(int i = 0; i < RESERVED.length ; i++){
			if (id.compareTo(RESERVED[i]) == 0){
				return true;
			}
		}
		return false;
	}

	private boolean isDigit(char startChar) {
		for (int i = 0; i < DIGITSET.length(); i++) {
			if (startChar == DIGITSET.charAt(i))
				return true;
		}
		return false;
	}

	private boolean isLetter(char startChar) {
		//LETTERSET.contains(""+startChar);
		
		for (int i = 0; i < LETTERSET.length(); i++) {
			if (startChar == LETTERSET.charAt(i))
				return true;
		}
		return false;
	}

	// how will you handle EOL
	// handle letter digits in the code
	private boolean isComment(char startChar1, char startChar2) {
		if (startChar1 == '/' & startChar2 == '/')
			return true;
		return false;
	}

	private boolean isOperator(char startChar) {
		for (int i = 0; i < OPERATORSET.length(); i++) {
			if (startChar == OPERATORSET.charAt(i))
				return true;
		}
		return false;
	}
}
